package config;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEParameterSpec;
import java.util.Properties;
import java.io.OutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.File;
import java.io.FileInputStream;

public class EncryptedProperties extends Properties {
  private static sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder();
  private static sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder();
  private Cipher encrypter, decrypter;
  private static byte[] salt = {0x03, 0x13, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12};  // make up your own
  private String _fileName;

  public EncryptedProperties(String password) throws Exception {
    PBEParameterSpec ps = new javax.crypto.spec.PBEParameterSpec(salt, 20);
    SecretKeyFactory kf = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
    SecretKey k = kf.generateSecret(new javax.crypto.spec.PBEKeySpec(password.toCharArray()));
    encrypter = Cipher.getInstance("PBEWithMD5AndDES/CBC/PKCS5Padding");
    decrypter = Cipher.getInstance("PBEWithMD5AndDES/CBC/PKCS5Padding");
    encrypter.init(Cipher.ENCRYPT_MODE, k, ps);
    decrypter.init(Cipher.DECRYPT_MODE, k, ps);
  }

  public EncryptedProperties(String password, String fileName) throws Exception {
    this(password);
    File f = new File(fileName);
    if (f.isFile() && f.canRead() && f.canWrite()) {
      FileInputStream in = new FileInputStream(f);
      load(in);
      in.close();
      _fileName = fileName;
    } else{
      throw new IllegalArgumentException("Need to create file: " + f.getAbsolutePath());
    }
  }

  public String getProperty(String key) {
    try {
      return decrypt(super.getProperty(key));
    } catch (Exception e) {
      return null;
    }
  }

  public synchronized Object setProperty(String key, String value) {
    try {
      return super.setProperty(key, encrypt(value));
    } catch (Exception e) {
      return null;
    }
  }

  private synchronized String decrypt(String str) throws Exception {
    byte[] dec = decoder.decodeBuffer(str);
    byte[] utf8 = decrypter.doFinal(dec);
    return new String(utf8, "UTF-8");
  }

  private synchronized String encrypt(String str) throws Exception {
    byte[] utf8 = str.getBytes("UTF-8");
    byte[] enc = encrypter.doFinal(utf8);
    return encoder.encode(enc);
  }

  public String getPassword() {
    return getProperty("password");
  }

  public void setPasssWord(String s) {
    setProperty("password", s);
  }

  public void store() throws IOException {
    File f = new File(_fileName);
    FileOutputStream out = new FileOutputStream(f);
    store(out, "Google");
    out.close();
  }

  public String getUsername() {
    String username = getProperty("username");
    if(username == null){
      throw new IllegalStateException("Need to run setup first.");
    }
    return username;
  }

  public void setUsername(String s) {
    setProperty("username", s);
  }

  public static EncryptedProperties getDefaultInstance() throws Exception {
    String masterPassword = ConsoleUtil.readSecret("master password");
    return new EncryptedProperties(masterPassword, "data.txt");
  }
}

